#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include "chrdev_demo.h"


struct demo_desc *demo_dev = NULL;

ssize_t demo_read(struct file *filep, char __user *buff, size_t size, loff_t *fpos)
{
    int ret;
    int value = 1;
    printk("-----%s-----\n", __FUNCTION__);

    ret = copy_to_user(buff, &value, sizeof(int));
    if (ret) {
        printk("copy to user error!\n");
        return -EFAULT;
    }
    return 0;
}

ssize_t demo_write(struct file *filep, const char __user *buff, size_t size, loff_t *fpos)
{
    int ret;
    unsigned int value = 0;
    printk("-----%s-----\n", __FUNCTION__);
    ret = copy_from_user(&value, buff, sizeof(int));
    if (ret) {
        printk("copy from user error!\n");
        return -EFAULT;
    }
    printk("-----%s-----, value = %d\n", __FUNCTION__, value);

    return 0;
}


static long ioctl_io(struct file *filep, unsigned long arg)
{
    printk("-----%s-----\n", __FUNCTION__);

    return 0;
}

static long ioctl_ior(struct file *filep, unsigned long arg)
{
    int ret;
    struct demo_msg msg = {
        .name = "chrdev",
        .value = 4,
        .data = "chrdev test",
    };
    printk("-----%s-----\n", __FUNCTION__);
    ret = copy_to_user((void __user *)arg, (void *)&msg, sizeof(struct demo_msg));
    if (ret) {
        printk("copy_to_user failed\n");
        return ret;
    }
    return 0;
}

static long ioctl_iow(struct file *filep, unsigned long arg)
{
    int ret;
    struct demo_msg msg;
    ret = copy_from_user((void *)&msg, (void __user *)arg, sizeof(struct demo_msg));
    if (ret) {
        printk("copy_from_user failed\n");
        return ret;
    }
    printk("name = %s, value = %d\n", msg.name, msg.value);
    return 0;
}

static long ioctl_iowr(struct file *filep, unsigned long arg)
{
    int ret;
    struct demo_msg msg;
    ret = copy_from_user((void *)&msg, (void __user *)arg, sizeof(struct demo_msg));
    if (ret) {
        printk("copy_from_user failed\n");
        return ret;
    }
    printk("name = %s, value = %d\n", msg.name, msg.value);

    strncpy(msg.name, "demochr", strlen("demochr") + 1);
    msg.value = -1;
    ret = copy_to_user((void __user *)arg, (void *)&msg, sizeof(struct demo_msg));
    if (ret) {
        printk("copy_to_user failed\n");
        return ret;
    }

    return 0;
}


struct ioctl_cmd demo_ioctl_cmd[] = {
    { IOCTL_IO,   ioctl_io },
    { IOCTL_IOR,  ioctl_ior },
    { IOCTL_IOW,  ioctl_iow },
    { IOCTL_IOWR, ioctl_iowr },
};

long demo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    int i;
    printk("-----%s-----\n", __FUNCTION__);
    for (i = 0; i < ARRAY_SIZE(demo_ioctl_cmd); i++) {
        if (cmd == demo_ioctl_cmd[i].cmd) {
           demo_ioctl_cmd[i].func(filep, arg); 
        }
    }
    return 0;
}

int demo_open(struct inode *inode, struct file *filep)
{
    printk("-----%s-----\n", __FUNCTION__);
    return 0;
}

int demo_release(struct inode *inode, struct file *filep)
{
    printk("-----%s-----\n", __FUNCTION__);
    return 0;
}

static const struct file_operations demo_ops = {
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
    .open = demo_open,
    .release = demo_release,
};

static void hardware_init(volatile void *addr)
{
    printk("-----%s-----\n", __FUNCTION__);
}

static int __init chrdev_demo_init(void)
{
    int ret;
    printk("-----%s-----\n", __FUNCTION__);
    demo_dev = kzalloc(sizeof(struct demo_desc), GFP_KERNEL);
    if (IS_ERR(demo_dev)) {
        printk(KERN_ERR "malloc error!\n");
        ret = -ENOMEM;
        goto err0;
    }

    demo_dev->dev_major = register_chrdev(0, CHRNAME, &demo_ops);
    if (demo_dev->dev_major < 0) {
        printk(KERN_ERR "register chrdev faidemo!\n");
        ret = -ENODEV;
        goto err1;
    }

    demo_dev->pcls = class_create(THIS_MODULE, CLASSNAME);
    if (IS_ERR(demo_dev->pcls)) {
        printk(KERN_ERR "create demo class faidemo!\n");
        ret = PTR_ERR(demo_dev->pcls);
        goto err2;
    }

    demo_dev->pdev = device_create(demo_dev->pcls, NULL, MKDEV(demo_dev->dev_major, 0), NULL, DEVICENAME);
    if (IS_ERR(demo_dev->pdev)) {
        printk(KERN_ERR "create demo device faidemo!\n");
        ret = PTR_ERR(demo_dev->pdev);
        goto err3;
    }

    demo_dev->reg_virt_base = ioremap(BASE_ADDR, BASE_SIZE);
    if (IS_ERR(demo_dev->reg_virt_base)) {
        printk("ioremap error\n");
        ret = -ENOMEM;
        goto err4;
    }

    hardware_init(demo_dev->reg_virt_base);

    return 0;
err4:
    device_destroy(demo_dev->pcls, MKDEV(demo_dev->dev_major, 0));
err3:
    class_destroy(demo_dev->pcls);
err2:
    unregister_chrdev(demo_dev->dev_major, CHRNAME);
err1:
    kfree(demo_dev);
err0:
    return ret;
}

static void __exit chrdev_demo_exit(void)
{
    printk("-----%s-----\n", __FUNCTION__);
    iounmap(demo_dev->reg_virt_base);
    device_destroy(demo_dev->pcls, MKDEV(demo_dev->dev_major, 0));
    class_destroy(demo_dev->pcls);
    unregister_chrdev(demo_dev->dev_major, CHRNAME);
    kfree(demo_dev);
}

module_init(chrdev_demo_init);
module_exit(chrdev_demo_exit);
MODULE_LICENSE("GPL");
